﻿using System.IO;
using System.Linq;
using MLSharp.Utilities;
using SVM;

namespace MLSharp.SupportVectorMachines
{
	/// <summary>
	/// Contains helper extension methods that are specific to LibSvm.
	/// </summary>
	internal static class LibSvmHelper
	{
		/// <summary>
		/// Converts the instances in the specified data set to a node set.
		/// </summary>
		/// <param name="dataSet"></param>
		/// <returns></returns>
		public static Node[][] GetInstancesAsNodes(this IDataSet dataSet)
		{
			Node[][] results = new Node[dataSet.Instances.Count][];

			//Convert each instance into an array of nodes
			for (int i = 0; i < dataSet.Instances.Count; i++)
			{
				//Count the number of values we need to convert into
				//nodes.  We won't convert the target attribute.
				int valueCount = dataSet.Attributes.Count - 1;

				Node[] values = new Node[valueCount];

				//The index for the next value.
				int valueIndex = 0;

				for (int j = 0; j < dataSet.Attributes.Count; j++)
				{
					if (j == dataSet.TargetAttributeIndex)
					{
						continue;
					}


					double value = (dataSet.Attributes[j].IsNumeric() || dataSet.Attributes[j].PossibleValues.Length != 2)
									//The instance is either numeric or not a binary set
					               	? dataSet.Instances[i].Values[j]
									//Otherwise, the instance is binary.
									: (dataSet.Instances[i].Values[j] == 0 ? -1.0 : 1.0);

					//libsvm uses 0-based indexing, so incremeneting valueIndex before it is assigned to 
					//the new node shifts everything up by on value, which is what we want.
					values[valueIndex++] = new Node(valueIndex, value);
				}

				results[i] = values;
			}

			return results;
		}

		/// <summary>
		/// Gets the class labels for all the items in the specified data set.
		/// </summary>
		/// <param name="dataSet"></param>
		/// <returns></returns>
		public static double[] GetClassLabels(this IDataSet dataSet)
		{
			return dataSet.Instances.Select(instance => instance.Values[dataSet.TargetAttributeIndex]).ToArray();
		}

		/// <summary>
		/// Performs a grid search to find the best parameters for the the support vector machine
		/// for the specified data set.
		/// </summary>
		/// <param name="workingDirectory">The working directory to use.</param>
		/// <param name="parameters">The LibSVM parameters.</param>
		/// <param name="dataSet">The data set to use for testing.</param>
		public static void FindOptimalParameters(string workingDirectory, Parameter parameters, IDataSet dataSet)
		{
			string parameterFile = Path.Combine(workingDirectory, NameGenerator.GetRandomName(10) + ".parameters");

			//Load the training data.
			Problem problem = new Problem(dataSet.Instances.Count, dataSet.GetClassLabels(), dataSet.GetInstancesAsNodes(),
										  //This is the one-based index of the last independent attribute.
			                              dataSet.Attributes.Count - 1);

			double cValue;
			double gammaValue;

			ParameterSelection.Grid(problem, parameters, parameterFile, out cValue, out gammaValue);

			parameters.C = cValue;
			parameters.Gamma = gammaValue;

			File.Delete(parameterFile);
		}
	}
}
